fix: port critical fixes from opencode-claude-auth for billing parity - #48
JJValentin wants to merge 1 commit into
Conversation
Ports 10 fixes discovered through code-level comparison with opencode-claude-auth (griffinmartin/opencode-claude-auth v1.4.10), verified with 38 unit tests and live-tested on an OpenClaw Max subscription. Critical fixes: - Add mcp_ prefix to all tool renames (Anthropic rejects bare PascalCase) - Compute real CCH hash (was hardcoded cch=00000) - Model-aware beta flags: exclude interleaved-thinking for Haiku, add effort only for 4.6 models, remove fake betas Reliability fixes: - Strip effort parameter for Haiku models (prevents 400 errors) - Direct OAuth token refresh via POST to claude.ai/v1/oauth/token with pre-expiry check, 401 retry, and race-condition dedup - Improved system prompt boundary detection (6 identity markers, generic Windows drive letter patterns, bounded to system[] array) Cleanup fixes: - Disable CC tool stubs by default (fixes zacdcook#43 tool-not-found loops) - Add orphaned tool_use/tool_result pair repair - Remove non-existent beta flags (advanced-tool-use, fast-mode) - Update Stainless SDK version to 0.90.0 Also adds test-functions.js with 38 unit tests covering all pure functions (no credentials or network required to run). Fixes zacdcook#41, zacdcook#43, zacdcook#46. Partially addresses zacdcook#47 (token refresh).
|
You were able to get token refresh to work with these changes? Mine failed when I implemented this. |
…Code 'advanced-tool-use-2025-11-20' and 'fast-mode-2026-02-01' were added speculatively in v2.0.0 but neither flag ever shipped in real Claude Code. Anthropic's density-based classifier scores the full anthropic-beta header, and unknown flags are a strong signal that the client is not genuine CC, contributing to extra-usage billing (zacdcook#41) and possibly to account revocation (zacdcook#37). Identified via PR zacdcook#48 code comparison against opencode-claude-auth v1.4.10 which verified the real CC beta flag set.
|
Not so sure about this PR. Here is what Opus has to say primarily regarding the MCP change: Let me be straightforward about my confidence level on each: |
|
Hey @DBostik, The Opus analysis was wrong on the mcp_ point. Built-ins included (mcp_Bash, mcp_Read, mcp_Write, mcp_Edit, mcp_Grep, mcp_Glob, etc.). There are no bare PascalCase names. You can verify this yourself by inspecting tool definitions in any active CC session. The fixes were derived from comparing against opencode-claude-auth line by line. They solved the same billing issues; that's where I found the mcp_ prefix, CCH hash, and model-aware betas. I have it working live on both my Openclaw and Opencode set-ups. On token refresh, it works. Direct POST to claude.ai/v1/oauth/token with the refresh token. Pre-expiry check so it refreshes before dying, 401 retry if it does expire, and dedup so concurrent requests don't race. What error were you hitting? |
I actually was able to get the token refresh to work thankfully. Haven't implemented the MCP or CCH changes yet though since it seems to be working as is. I'm definitely having cache issues causing extreme token usage though but I believe that is a LCM issue. |
|
@DBostik The CCH fix might actually be what addresses the cache issue. The hardcoded key means cache keys never match, so every turn bills as fresh tokens. Computing the real hash is what makes prompt caching work. |
Had to ask Opus of course and this is what I got(also confirmation from LCM team that they are aware of my issue and have a fix): The claim that hardcoded cch=00000 breaks caching is not accurate for your setup. In fact, the opposite is true -- changing it to a computed per-request hash would make things worse. |
- Remove web_search/web_fetch from Layer 3 tool renames (same class of bug as the image rename, issue zacdcook#14). The rename corrupts the "type" field of built-in tools, causing Anthropic to reject with: Input tag WebSearch found using type does not match expected tags - Add Layer 0: auto-normalize shorthand built-in tool types to their latest versioned names (e.g. "web_search" -> "web_search_20260209"). Covers all 8 built-in tool types: web_search, web_fetch, text_editor, code_execution, bash, memory, tool_search_tool_bm25, tool_search_tool_regex - Update REQUIRED_BETAS: remove density-classifier signals (advanced-tool-use-2025-11-20, fast-mode-2026-02-01) identified via PR zacdcook#48 comparison against opencode-claude-auth - Add per-model beta filtering (getModelBetas): skip interleaved-thinking for Haiku, skip effort for non-4.6 models - Update Layer 2 replacements with improved obfuscation patterns
Ports 8 substitutions derived from upstream PR zacdcook#48 by JJValentin, which itself ports 10 fixes from opencode-claude-auth v1.4.10 (verified in production on OpenClaw Max subscription). Our fork applies only the BODY-LEVEL changes. HTTP-header-level changes in PR zacdcook#48 (beta header, Stainless version in transit) are either dead weight (we don't send Anthropic-Beta) or overridden by downstream CLIProxyAPI applyClaudeHeaders(). See README §三 for details. Applied changes: - CC_TOOL_STUBS: mcp_ prefix on all 5 stubs (mcp_Glob, mcp_Grep, mcp_Agent, mcp_NotebookEdit, mcp_TodoRead) - DEFAULT_TOOL_RENAMES: mcp_ prefix on 13 non-CC tools (pdf, image_generate, music_generate, video_generate, memory_search, memory_get, lcm_expand_query, lcm_grep, lcm_describe, lcm_expand, yield_task, task_store, task_yield_interrupt). CC-native tools (Read/Write/Bash/etc) stay PascalCase without mcp_. - CCH: add computeCch() helper; buildBillingBlock now emits real SHA256(firstUserText)[:5] instead of hardcoded cch=00000. - buildBillingBlock: accepts optional preExtractedText so CCH is computed from the ORIGINAL user text (pre-Layer-2 transformation). - Layer 1 call site: passes originalFirstUserText saved at the top of processBody(). - Layer 2.5: Haiku effort stripping (detects haiku in model name, strips effort from output_config and thinking objects — Haiku returns 400 if effort is present). - New helpers: findMatchingBrace (string-aware {/} matcher) and stripEffortFromObject. - repairToolPairs(): removes orphaned tool_use/tool_result blocks before any transforms run. Prevents Anthropic 400 errors on conversation histories with unmatched tool blocks (issue zacdcook#34). Includes adjacent-same-role guard. - x-stainless-package-version: bumped 0.81.0 -> 0.90.0 (this field is stripped/replaced by CLIProxyAPI in Plan B; kept for upstream parity and standalone deployments). Verified on production: - node -c proxy.js passes - 3 smoke tests: Sonnet 4.6 (Messages), Opus 4.6 (OpenAI-compat), Haiku with effort all returned 200 - [EFFORT] log line confirms Layer 2.5 triggered for Haiku - 28 real requests in 5 minutes post-deploy: 0 errors
Rewrites README around this fork's actual deployment pattern: chained through CLIProxyAPI :18801, not direct to api.anthropic.com. The original README was written for standalone use and misrepresented what the fork does in production. Main additions: - §零 three-line fork summary - §一 full Plan B architecture diagram (client -> nginx -> logger / billing-proxy :18804 -> CLIProxyAPI :18801 -> upstream) - §二 comprehensive body-level transform table (all 10+ layers including PR zacdcook#48 additions) - §三 ZOMBIE CODE section — documents that REQUIRED_BETAS, getModelBetas, getStainlessHeaders, and UA injection are RETAINED but NOT EFFECTIVE in Plan B because CLIProxyAPI's applyClaudeHeaders() scrubs and rebuilds all HTTP headers. Includes pointer: "want to change HTTP-level spoofing? edit CLIProxyAPI, not this repo." - §四 complete deployment guide: prerequisites, CLIProxyAPI config snippets, billing-proxy config.json, systemd unit (with Requires=cliproxyapi.service), nginx route examples, client config examples - §五 updated tool rename tables split into "CC-native (no prefix)" vs "non-CC (mcp_ prefix)" - §八 troubleshooting matrix for the full chain - §九 version history entry for PR zacdcook#48 port - §十 pointers to sidecar tools (billing-logger, claude-watchdog) that live outside this repo but complete the Plan B picture Rationale for the body/header split emphasis: investigation confirmed that Anthropic-Beta, X-Stainless-*, User-Agent, X-Claude-Code-Session- Id, x-client-request-id, and CCH signing are all handled exclusively by CLIProxyAPI in the Plan B path. Our proxy's remaining value is purely body-level (tools, messages, system content, billing text block, path protection, prompt caching). Future contributors should not attempt to fix HTTP-header-level spoofing here.
Previously the rename table had an inconsistent mix: 7 entries mapping to REAL CC tools (Bash/Read/Write/Edit/Grep/Glob/LS — no prefix) and 14 entries mapping to PascalCase names that LOOK like CC tools but aren't (BashSession/BrowserControl/TaskCreate/etc — also no prefix). PR zacdcook#48 added mcp_ prefix only to 13 of the most obviously non-CC tools (pdf/music_generate/lcm_*/yield_task etc). The 14 middle-ground fake-CC names were left bare, creating an inconsistent state where Anthropic's CC-tool-set density classifier would see names that neither match real CC nor mark themselves as MCP-user-tools. This commit applies mcp_ prefix to all 14 remaining non-CC entries: - process → BashSession → mcp_BashSession - browser → BrowserControl → mcp_BrowserControl - canvas → CanvasView → mcp_CanvasView - cron → Scheduler → mcp_Scheduler - message → SendMessage → mcp_SendMessage - tts → Speech → mcp_Speech - gateway → SystemCtl → mcp_SystemCtl - agents_list → AgentList → mcp_AgentList - create_task → TaskCreate → mcp_TaskCreate - list_tasks → TaskList → mcp_TaskList - get_history → TaskHistory → mcp_TaskHistory - send_to_task → TaskSend → mcp_TaskSend - subagents → AgentControl → mcp_AgentControl - session_status → StatusCheck → mcp_StatusCheck Result: the rename table now has a clean binary split: - 7 entries → real CC tool names (no prefix, Anthropic sees CC) - 14 entries → mcp_ prefixed names (Anthropic sees user MCP tools) REVERSE MAPPING: no changes needed. reverseMap() iterates config .toolRenames in swapped direction, so forward update is symmetric. DEFAULT_REVERSE_MAP handles only Layer-2 sanitize reverse and didn't reference these names. Verified end-to-end: sent a request with "process" tool defined, model responded with tool_use name="process" (not mcp_BashSession), proving the full chain process -> mcp_BashSession -> Anthropic -> mcp_BashSession -> process -> client works correctly. README §二 Layer 3 row updated, §五 split into table 2 (real CC, 7 entries) and table 3 (mcp_ prefix, 28 entries), §九 version history adds this change.
…t strip, multi-marker identity, tests - OAuth token refresh: async getTokenAsync with race-condition dedup, disk persistence, and 401 cache invalidation - Layer 2.5: stripEffortFromObject removes effort params for Haiku models (prevents 400 errors) - Layer 4: multi-marker identity detection (6 variants) with improved end-boundary patterns (UNC paths, generic Windows drives) - injectCCStubs defaults to false (issue zacdcook#43 — prevents tool-not-found loops) - Stainless SDK version bump 0.81.0 → 0.90.0 - Async request handler for OAuth await support - 51 unit tests in test-functions.js covering all pure functions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Ports 10 fixes discovered through a code-level comparison with opencode-claude-auth v1.4.10, which solved the same billing-parity problems for OpenCode. All changes are verified with 38 unit tests and live-tested on an OpenClaw Max subscription (Opus 4.6 calls working through the proxy).
What Changed
🔴 Critical Fixes (requests failing without these)
mcp_tool prefixBash) — must bemcp_BashDEFAULT_TOOL_RENAMESentries now includemcp_prefixcch=00000— Anthropic may validate this hashSHA256(text).hex().slice(0,5)from original (pre-transform) user messageinterleaved-thinkingsent to Haiku (400 error).effortbeta sent to 4.5 models. Two fake betas (advanced-tool-use-2025-11-20,fast-mode-2026-02-01) never existed in Claude Code.getModelBetas(modelId)filters by model. Fake betas removed.🟡 Reliability Fixes (things that break over time)
effortparameter with 400effortfromoutput_configandthinkingobjectsPOST claude.ai/v1/oauth/tokenwith pre-expiry check, 401 retry, race-condition dedup, and disk persistenceC:\,D:\,E:\drives and could match outsidesystem[][A-Z]:\drive pattern, search bounded tosystem[]viafindMatchingBracket()🟢 Cleanup
injectCCStubsdefaults tofalse. Claude Code doesn't inject fake tools.tool_use/tool_resultblocks cause API errorsrepairToolPairs()function removes orphans with adjacent same-role turn guardadvanced-tool-use-2025-11-20andfast-mode-2026-02-01don't exist in Claude CodeREQUIRED_BETAS0.81.0is stale0.90.0Testing
test-functions.js(run withnode test-functions.js, zero dependencies)node -c proxy.jspassesIssues Addressed
usageto diagnose token burn; possible cache-key contamination via dynamic fingerprint (cc #41) #46 (CCH now computed from pre-transform text, not post-transform)Reference
All fixes were derived from opencode-claude-auth v1.4.10 (specifically:
src/transforms.ts,src/signing.ts,src/credentials.ts,src/model-config.ts,src/betas.ts). That project solved the same billing-parity problems for OpenCode and has been iterating on Anthropic's detection changes since March 2026.